﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace RegexEditor
{
	/// <summary>
	/// A rich text box component with syntax highlighting capabilities and unlimited undo/redo buffers.
	/// </summary>
	public class HighlightTextBox : RichTextBox
	{
		#region Private Fields

		private Stack<BufferItem> _undoBuffer = new Stack<BufferItem>();
		private Stack<BufferItem> _redoBuffer = new Stack<BufferItem>();
		private BufferDirection _bufferDirection;
		private bool _isTrackingChanges;

		#endregion

		#region Constructors

		public HighlightTextBox()
		{
			InitializeComponent();
		}

		#endregion

		#region Public Members

		/// <summary>
		/// Performs highlighting of a selection.
		/// </summary>
		/// <param name="start">The selection start.</param>
		/// <param name="length">The selection length.</param>
		/// <param name="foreColor">The foreground color.</param>
		/// <param name="backColor">The background color.</param>
		public virtual void Highlight(int start, int length, Color foreColor, Color backColor)
		{
			using (NamedLock.AcquireLock("Highlighting"))
			{
				using (new TextBoxState(this))
				{
					SelectionStart = start;
					SelectionLength = length;

					if (!IgnoreTransparent)
					{
						SelectionColor = foreColor != Color.Transparent ? foreColor : SystemColors.WindowText;
						SelectionBackColor = backColor != Color.Transparent ? backColor : SystemColors.Window;
					}
					else
					{
						if (foreColor != Color.Transparent)
							SelectionColor = foreColor;
						if (backColor != Color.Transparent)
							SelectionBackColor = backColor;
					}
				}
			}
		}

		/// <summary>
		/// Removes all formatting, eliminating all highlighting effects.
		/// </summary>
		public void RemoveFormatting()
		{
			using (NamedLock.AcquireLock("RemovingFormatting"))
			{
				using (new TextBoxState(this))
				{
					_isTrackingChanges = false;
					string text = Text;
					Clear();
					Text = text;
					_isTrackingChanges = true;
				}
			}
		}

		/// <summary>
		/// Performs undo using the managed buffer.
		/// </summary>
		public void Undo2()
		{
			if (_undoBuffer.Count > 0)
			{
				_isTrackingChanges = false;

				if (_bufferDirection != BufferDirection.Backward)
				{
					BufferItem currentState = _undoBuffer.Pop();
					_redoBuffer.Push(currentState);
					_bufferDirection = BufferDirection.Backward;
				}

				if (_undoBuffer.Count > 0)
				{
					BufferItem state = _undoBuffer.Pop();
					state.Apply(this);
					_redoBuffer.Push(state);

					if (_undoBuffer.Count == 0)
						_bufferDirection = BufferDirection.Neutral;
				}

				_isTrackingChanges = true;
			}
		}

		/// <summary>
		/// Performs redo using the managed buffer.
		/// </summary>
		public void Redo2()
		{
			if(_redoBuffer.Count > 0)
			{
				_isTrackingChanges = false;

				if (_bufferDirection != BufferDirection.Forward)
				{
					BufferItem currentState = _redoBuffer.Pop();
					_undoBuffer.Push(currentState);
					_bufferDirection = BufferDirection.Forward;
				}

				if (_redoBuffer.Count > 0)
				{
					BufferItem state = _redoBuffer.Pop();
					state.Apply(this);
					_undoBuffer.Push(state);

					if (_redoBuffer.Count == 0)
						_bufferDirection = BufferDirection.Neutral;
				}

				_isTrackingChanges = true;
			}
		}

		#endregion

		#region Protected Members

		/// <summary>
		/// Gets or sets whether the transparent color should be ignored instead of using system colors.
		/// </summary>
		protected bool IgnoreTransparent { get; set; }

		/// <summary>
		/// Initializes the text box component, setting default properties.
		/// </summary>
		protected virtual void InitializeComponent()
		{
			AutoWordSelection = false;
			DetectUrls = false;
			_isTrackingChanges = true;
			_undoBuffer.Push(new BufferItem { Index = SelectionStart, Value = Text });
		}

		/// <summary>
		/// Fires when text is changes to update the managed buffer for undo/redo actions.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnTextChanged(EventArgs e)
		{
			if (_isTrackingChanges)
			{
				BufferItem state = new BufferItem { Index = SelectionStart, Value = Text };

				if (_undoBuffer.Count == 0 || !_undoBuffer.Peek().Equals(state))
				{
					_undoBuffer.Push(state);
					_redoBuffer.Clear();
					_bufferDirection = BufferDirection.Neutral;
				}
			}

			if (!NamedLock.IsLocked("RemovingFormatting") && !NamedLock.IsLocked("Highlighting"))
				base.OnTextChanged(e);
		}

		#endregion

		[System.Diagnostics.DebuggerDisplay("{Text},{SelectionStart}")]
		private struct BufferItem : IEquatable<BufferItem>
		{
			public int Index;
			public string Value;

			public void Apply(HighlightTextBox textBox)
			{
				using (new PaintLock(textBox))
				{
					textBox.Text = Value;
					textBox.SelectionStart = Index;
				}
			}

			public bool Equals(BufferItem other)
			{
				return Index == other.Index && Value == other.Value;
			}
		}

		private enum BufferDirection
		{
			Neutral,
			Backward,
			Forward
		}
	}
}